/*
 *  Copyright (C) 2010 Interactive Media Management
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.i2m.converge.plugins.joomla;

import dk.i2m.converge.core.plugin.WorkflowAction;
import dk.i2m.commons.FileUtils;
import dk.i2m.commons.ImageUtils;
import dk.i2m.commons.StringUtils;
import dk.i2m.converge.core.content.MediaItem;
import dk.i2m.converge.core.content.NewsItem;
import dk.i2m.converge.core.content.NewsItemActor;
import dk.i2m.converge.core.content.NewsItemMediaAttachment;
import dk.i2m.converge.core.metadata.Concept;
import dk.i2m.converge.core.plugin.PluginContext;
import dk.i2m.converge.core.security.UserAccount;
import dk.i2m.converge.core.workflow.WorkflowStepAction;
import dk.i2m.converge.core.workflow.WorkflowStepActionProperty;
import dk.i2m.joomla.JoomlaConnection;
import dk.i2m.joomla.JoomlaException;
import java.io.File;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Workflow action for sending a story to a Joomla website.
 *
 * @author Allan Lykke Christensen
 */
@dk.i2m.converge.core.annotations.WorkflowAction
public class JoomlaAction extends WorkflowAction {

    /** Adapter version for which the connector is compatible. */
    public static final String ADAPTER_COMPATIBILITY = "1.0";

    /** Name of the property containing the URL of the Joomla Adapter. */
    public static final String PROPERTY_URL = "joomla.url";

    /** Name of the method to invoke on the Converge XML-RPC API. */
    public static final String PROPERTY_METHOD = "joomla.method";

    /** Property containing the username for accessing the Joomla Adapter. */
    public static final String PROPERTY_USERNAME = "joomla.username";

    /** Property containing the password for accessing the Joomla Adapter. */
    public static final String PROPERTY_PASSWORD = "joomla.password";

    /** Property, if it exist, the news item will be posted immediately and the edition disregarded. */
    public static final String PROPERTY_POST_IMMEDIATELY = "post_immediately";

    /** Property, if it exist, the posting of the news item will delay for the number of minutes specified. */
    public static final String PROPERTY_POST_DELAY = "post_delay";

    /** Property, if it exist, it will use it to determine after how many hours, the news item should expire. */
    public static final String PROPERTY_EXPIRE_AFTER = "expire_after";

    /** Property, if it exist, the news item will never expire automatically. */
    public static final String PROPERTY_EXPIRE_NEVER = "expire_never";

    /** Property containing the undisclosed author label for articles with undisclosed aurhors. */
    public static final String PROPERTY_UNDISCLOSED_AUTHOR_LABEL = "undisclosed_author_label";

    /** Name of the property containing media version label to use when uploading news items. */
    public static final String PROPERTY_MEDIA_TAG = "media.tag";

    private static final String PROPERTY_IMAGE_RESIZE_WIDTH = "image.resize.width";

    private static final String PROPERTY_IMAGE_RESIZE_HEIGHT = "image.resize.height";

    /** Property containing the mapping of a single section in the system with a category in Joomla. */
    public static final String PROPERTY_CATEGORY_MAPPING = "category_mapping";

    public static final String PROPERTY_FRONTPAGE_MAPPING = "mapping.frontpage";

    /** XML RPC method for determining the version of the adapter. */
    public static final String XMLRPC_METHOD_VERSION = "converge.version";

    /** XML RPC method for submitting a new article. */
    public static final String XMLRPC_METHOD_NEW_ARTICLE = "converge.newArticle";

    /** XML RPC method for updating an existing article. */
    public static final String XMLRPC_METHOD_UPDATE_ARTICLE = "converge.editArticle";

    /** XML RPC method for deleting an existing article. */
    public static final String XMLRPC_METHOD_DELETE_ARTICLE = "converge.deleteArticle";

    /** XML RPC method for uploading a photo. */
    public static final String XMLRPC_METHOD_NEW_MEDIA = "converge.newMedia";

    private Map<String, String> availableProperties = null;

    private static final Logger logger = Logger.getLogger(JoomlaAction.class.getName());

    private ResourceBundle msgs = ResourceBundle.getBundle("dk.i2m.converge.plugins.joomla.JoomlaAction");

    private Calendar releaseDate = new GregorianCalendar(2010, Calendar.JULY, 31, 00, 43);

    private Map<String, String> properties = new HashMap<String, String>();

    private Map<String, String> categoryMapping = new HashMap<String, String>();

    /**
     * Creates a new instance of {@link JoomlaAction}.
     */
    public JoomlaAction() {
    }

    @Override
    public void execute(PluginContext ctx, NewsItem item, WorkflowStepAction stepAction, UserAccount user) {
        this.properties = stepAction.getPropertiesAsMap();
        this.categoryMapping = new HashMap<String, String>();

        for (WorkflowStepActionProperty prop : stepAction.getProperties()) {
            if (prop.getKey().equalsIgnoreCase(PROPERTY_CATEGORY_MAPPING)) {
                String[] catMap = prop.getValue().split(";");
                if (catMap.length == 2) {
                    categoryMapping.put(catMap[0], catMap[1]);
                } else {
                    logger.log(Level.INFO, "Invalid category mapping: {0}", prop.getValue());
                }
            }
        }

        if (!properties.containsKey(JoomlaAction.PROPERTY_URL)) {
            logger.log(Level.WARNING, "{0} property missing from action properties", JoomlaAction.PROPERTY_URL);
            return;
        }

        String method = "";
        if (!properties.containsKey(JoomlaAction.PROPERTY_METHOD)) {
            logger.log(Level.WARNING, "{0} property missing from action properties", JoomlaAction.PROPERTY_METHOD);
            return;
        } else {
            method = properties.get(JoomlaAction.PROPERTY_METHOD);
        }

        if (!properties.containsKey(JoomlaAction.PROPERTY_USERNAME)) {
            logger.log(Level.WARNING, "{0} property missing from action properties", JoomlaAction.PROPERTY_USERNAME);
            return;
        }

        if (!properties.containsKey(JoomlaAction.PROPERTY_PASSWORD)) {
            logger.log(Level.WARNING, "{0} property missing from action properties", JoomlaAction.PROPERTY_PASSWORD);
            return;
        }

        JoomlaConnection connection = new JoomlaConnection();
        connection.setUrl(properties.get(JoomlaAction.PROPERTY_URL));
        connection.setUsername(properties.get(JoomlaAction.PROPERTY_USERNAME));
        connection.setPassword(properties.get(JoomlaAction.PROPERTY_PASSWORD));
        connection.setTimeZone(user.getTimeZone());

        if (!connection.isConnectionValid()) {
            logger.log(Level.WARNING, "Connection invalid");
            return;
        }

        // Default Width
        if (!properties.containsKey(JoomlaAction.PROPERTY_IMAGE_RESIZE_WIDTH)) {
            properties.put(JoomlaAction.PROPERTY_IMAGE_RESIZE_WIDTH, "640");

        }

        // Default Height
        if (!properties.containsKey(JoomlaAction.PROPERTY_IMAGE_RESIZE_HEIGHT)) {
            properties.put(JoomlaAction.PROPERTY_IMAGE_RESIZE_WIDTH, "480");
        }

        if (method.equalsIgnoreCase(JoomlaAction.XMLRPC_METHOD_NEW_ARTICLE)) {

            try {
                newArticle(connection, item);
            } catch (JoomlaActionException ex) {
                logger.log(Level.SEVERE, ex.getMessage());
                logger.log(Level.FINE, "", ex);
            }
        } else if (method.equalsIgnoreCase(JoomlaAction.XMLRPC_METHOD_DELETE_ARTICLE)) {
            try {
                deleteArticle(connection, item);
            } catch (JoomlaActionException ex) {
                logger.log(Level.SEVERE, ex.getMessage());
                logger.log(Level.FINE, "", ex);
            }
        }
    }

    @Override
    public Map<String, String> getAvailableProperties() {
        if (availableProperties == null) {
            availableProperties = new LinkedHashMap<String, String>();
            availableProperties.put("Joomla XML-RPC URL", PROPERTY_URL);
            availableProperties.put("Method to execute", PROPERTY_METHOD);
            availableProperties.put("Joomla username", PROPERTY_USERNAME);
            availableProperties.put("Joomla password", PROPERTY_PASSWORD);
            availableProperties.put("Category mapping (Converge Section ID;Joomla Category ID)", PROPERTY_CATEGORY_MAPPING);
            availableProperties.put("Front page mapping", PROPERTY_FRONTPAGE_MAPPING);
            availableProperties.put("Undisclosed author label", PROPERTY_UNDISCLOSED_AUTHOR_LABEL);
            availableProperties.put("Post immediately (disregard edition)", PROPERTY_POST_IMMEDIATELY);
            availableProperties.put("Expire after (disregard edition)", PROPERTY_EXPIRE_AFTER);
            availableProperties.put("Never expire", PROPERTY_EXPIRE_NEVER);
            availableProperties.put("Delay posting (hours)", PROPERTY_POST_DELAY);
            availableProperties.put("Media version label", PROPERTY_MEDIA_TAG);
            availableProperties.put("Image resize (width)", PROPERTY_IMAGE_RESIZE_WIDTH);
            availableProperties.put("Image resize (height)", PROPERTY_IMAGE_RESIZE_HEIGHT);
        }
        return availableProperties;
    }

    @Override
    public String getName() {
        return msgs.getString("PLUGIN_NAME");
    }

    @Override
    public String getDescription() {
        return msgs.getString("PLUGIN_DESCRIPTION");
    }

    @Override
    public String getVendor() {
        return msgs.getString("PLUGIN_VENDOR");
    }

    @Override
    public Calendar getDate() {
        return releaseDate;
    }

    private void newArticle(JoomlaConnection connection, NewsItem newsItem) throws JoomlaActionException {
        try {
            logger.log(Level.INFO, "Uploading media files for news item #{0}", new Object[]{newsItem.getId()});
            List<UploadedMediaFile> mediaFiles = uploadMediaItems(connection, newsItem);

            Integer foreignId = connection.newArticle(String.valueOf(newsItem.getId()),
                    newsItem.getTitle(), generateIntro(newsItem),
                    generateStory(newsItem, mediaFiles), generateAuthors(newsItem),
                    generateCategoryId(newsItem), generateFrontPage(newsItem),
                    String.valueOf(newsItem.getPosition()),
                    generateKeywords(newsItem), generateIntro(newsItem),
                    generatePublishDate(newsItem), generateExpireDate(newsItem));

            if (foreignId != null) {
                logger.log(Level.INFO, "News item #{0} created or updated in Joomla with article id #{1}", new Object[]{newsItem.getId(), foreignId});
            } else {
                logger.log(Level.WARNING, "Foreign key was not received from Converge Joomla API");
            }

        } catch (JoomlaException ex) {
            throw new JoomlaActionException(ex);
        }
    }

    private void deleteArticle(JoomlaConnection connection, NewsItem newsItem) throws JoomlaActionException {
        try {
            logger.log(Level.INFO, "Deleting news item #{0} from Joomla", new Object[]{newsItem.getId()});
            connection.deleteArticle(String.valueOf(newsItem.getId()));
        } catch (JoomlaException ex) {
            throw new JoomlaActionException(ex);
        }
    }

    private List<UploadedMediaFile> uploadMediaItems(JoomlaConnection connection, NewsItem newsItem) {
        String mediaLabel = properties.get(PROPERTY_MEDIA_TAG);
        MediaItem webVersion = null;
        List<UploadedMediaFile> uploadedImages = new ArrayList<UploadedMediaFile>();
        for (NewsItemMediaAttachment attachment : newsItem.getMediaAttachments()) {
            MediaItem item = attachment.getMediaItem();
            if (item != null) {
                if (item.isFileAttached()) {

                    if (item.getLabel() != null && item.getLabel().getName().equalsIgnoreCase(mediaLabel)) {
                        webVersion = item;
                    } else {
                        for (MediaItem versionItem : item.getVersions()) {
                            if (versionItem.getLabel() != null && versionItem.getLabel().getName().equalsIgnoreCase(mediaLabel)) {
                                webVersion = versionItem;
                            }
                        }
                    }

                    if (webVersion == null) {
                        logger.log(Level.WARNING, "Web version ({0}) missing for Media Item #{1}", new Object[]{mediaLabel, item.getId()});
                    }

                    String filename = "";
                    byte[] filedata;
                    try {
                        if (webVersion == null) {
                            filename = newsItem.getId() + "-" + item.getId() + "." + item.getExtension();

                            URL mediaFile = new URL(item.getAbsoluteFilename());
                            filedata = ImageUtils.generateThumbnail(FileUtils.getBytes(mediaFile), Integer.valueOf(properties.get(PROPERTY_IMAGE_RESIZE_WIDTH)), Integer.valueOf(properties.get(PROPERTY_IMAGE_RESIZE_HEIGHT)), 100);

                        } else {
                            filename = newsItem.getId() + "-" + webVersion.getId() + "." + webVersion.getExtension();
                            filedata = FileUtils.getBytes(new File(webVersion.getAbsoluteFilename()));
                        }
                    } catch (Exception ex) {
                        logger.log(Level.WARNING, ex.getMessage());
                        logger.log(Level.FINE, "", ex);
                        break;
                    }


                    try {
                        String webImgLocation = connection.uploadMediaFile(String.valueOf(newsItem.getId()), filename, filedata);
                        uploadedImages.add(new UploadedMediaFile(webImgLocation, attachment.getCaption()));
                    } catch (JoomlaException ex) {
                        logger.log(Level.WARNING, "Could not upload media file", ex);
                    }

                } else {
                    logger.log(Level.WARNING, "No file attached to Media Item #{1}", new Object[]{item.getId()});
                }
            }
        }

        return uploadedImages;
    }

    private String generateIntro(NewsItem item) {
        String intro = StringUtils.stripHtml(item.getStory());
        intro = org.apache.commons.lang.StringUtils.abbreviate(intro, 450);
        return intro;
    }

    private String generateAuthors(NewsItem item) {
        if (item.isUndisclosedAuthor()) {
            return properties.get(PROPERTY_UNDISCLOSED_AUTHOR_LABEL);
        } else {
            if (item.getByLine().trim().isEmpty()) {
                StringBuilder by = new StringBuilder();

                for (NewsItemActor actor : item.getActors()) {
                    boolean firstActor = true;
                    if (actor.getRole().equals(item.getOutlet().getWorkflow().getStartState().getActorRole())) {
                        if (!firstActor) {
                            by.append(", ");
                        } else {
                            firstActor = false;
                        }
                        by.append(actor.getUser().getFullName());
                    }
                }
                return by.toString();
            } else {
                return item.getByLine();
            }
        }
    }

    public String generateCategoryId(NewsItem item) {
        if (item.getSection() == null) {
            if (categoryMapping.containsKey("0")) {
                return categoryMapping.get("0");
            } else {
                return "0";
            }
        }

        if (categoryMapping.containsKey(String.valueOf(item.getSection().getId()))) {
            return categoryMapping.get(String.valueOf(item.getSection().getId()));
        } else {
            return "0";
        }
    }

    private String generateStory(NewsItem item, List<UploadedMediaFile> mediaFiles) {
        String imgPattern = "<p><img class=\"caption\" src=\"{0}\" border=\"0\" alt=\"{1}\" title=\"{1}\" align=\"left\" /></p>";
        StringBuilder body = new StringBuilder();
        for (UploadedMediaFile umf : mediaFiles) {
            String img = MessageFormat.format(imgPattern, new Object[]{umf.getUrl(), umf.getCaption()});
            body.append(img);
        }
        body.append(item.getStory());
        return body.toString();
    }

    private boolean generateFrontPage(NewsItem item) {
        if (properties.containsKey(PROPERTY_FRONTPAGE_MAPPING)) {
            if (properties.get(PROPERTY_FRONTPAGE_MAPPING).equalsIgnoreCase(String.valueOf(item.getStart()))) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private String generateKeywords(NewsItem item) {
        StringBuilder keywords = new StringBuilder();
        boolean first = true;
        for (Concept concept : item.getConcepts()) {
            if (!first) {
                keywords.append(", ");
            } else {
                first = false;
            }

            keywords.append(concept.getName());
        }

        return keywords.toString();
    }

    private Calendar generatePublishDate(NewsItem item) {
        if (properties.containsKey(PROPERTY_POST_IMMEDIATELY) && properties.containsKey(PROPERTY_POST_DELAY)) {
            Calendar now = Calendar.getInstance();
            now.add(Calendar.HOUR_OF_DAY, Integer.valueOf(properties.get(PROPERTY_POST_DELAY)));
            return now;
        } else if (properties.containsKey(PROPERTY_POST_IMMEDIATELY)) {
            return null;
        } else if (properties.containsKey(PROPERTY_POST_DELAY)) {
            Calendar now = Calendar.getInstance();
            if (item.getEdition() != null) {
                now = item.getEdition().getPublicationDate();
            }
            now.add(Calendar.HOUR_OF_DAY, Integer.valueOf(properties.get(PROPERTY_POST_DELAY)));
            return now;
        } else {
            return item.getEdition().getPublicationDate();
        }
    }

    private Calendar generateExpireDate(NewsItem item) {
        if (properties.containsKey(PROPERTY_EXPIRE_NEVER)) {
            return null;
        } else {
            if (properties.containsKey(PROPERTY_EXPIRE_AFTER)) {
                Calendar now = Calendar.getInstance();
                now.add(Calendar.HOUR_OF_DAY, Integer.valueOf(properties.get(PROPERTY_EXPIRE_AFTER)));
                return now;
            } else {
                return item.getEdition().getExpirationDate();
            }
        }
    }

    public class UploadedMediaFile {

        private String url = "";

        private String caption = "";

        public UploadedMediaFile() {
            url = "";
            caption = "";
        }

        public UploadedMediaFile(String url, String caption) {
            this.url = url;
            this.caption = caption;
        }

        public String getCaption() {
            return caption;
        }

        public void setCaption(String caption) {
            this.caption = caption;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }
    }
}
